﻿using System;
using System.Collections.Generic;
using System.Threading;
using yafc.Interface.Api;
using System.Net;
using yafc.Data;

namespace yafc.Activity
{
	public class NewPostEventArgs : EventArgs
	{
		public ICollection<Post> posts = null;
	}

	public class Session : IDisposable
	{
		public event EventHandler<LoginResults> LoginResponse;
		public event EventHandler<SendResults> SendResponse;
		public event EventHandler<NewPostEventArgs> NewPost;

		private string name;
		private List<Account> acclist = new List<Account>();
		private Dictionary<string, Post> posts = new Dictionary<string, Post>();

		private TaskQueue tqueue = new TaskQueue();

		public string Name
		{
			get { return name; }
			set { name = value; }
		}

		public Session(string _Name)
		{
			Name = _Name;
			tqueue.Start();

			timer.Enabled = true;
			timer.Elapsed += new System.Timers.ElapsedEventHandler(OnTimer);
		}

		public void Login(IFactory f, string Serv, string Uname, string Passwd)
		{
			if (Contains(Serv, Uname))
			{
				throw new Exception("Account " + Uname + "@" + Serv + " has been added more than once.");
			}
			Account acc = Account.Create(f, Serv, Uname, Passwd);
			Attach(acc);
			BeginLogin(acc);
		}
		public void Relogin(string Serv, string Uname, string Passwd)
		{
			Relogin(GetAccount(Serv, Uname), Uname, Passwd);
		}
		protected void Relogin(Account Acc, string Uname, string Passwd)
		{
			Task t = new Task((p) => Acc.Relogin(Uname, Passwd));
			tqueue.Add(t);
		}

		public void Send(string text)
		{
			foreach (var acc in acclist)
			{
				BeginSend(acc, text);
			}
		}
		public void Reply(string Text, Data.Post replyTo)
		{
			BeginSend(replyTo.account, Text, replyTo.post.id);
		}

		public void Attach(Account NewAcc)
		{
			lock(acclist)
			{
				foreach (var acc in acclist)
				{
					if (acc == NewAcc)
					{
						return;
					}
				}
				acclist.Add(NewAcc);
			}

			NewAcc.TryDominate(this);
			NewAcc.Timeline += OnTimeline;
			NewAcc.LoginResults += OnLogin;
			NewAcc.SendResults += OnSend;
		}
		public void Remove(string Serv, string Uname)
		{
			var acc = GetAccount(Serv, Uname);
			Detach(acc);
		}
		protected void Detach(Account Acc)
		{
			lock(acclist)
				{ acclist.Remove(Acc); }
			Acc.TryFree(this);
			Account.Remove(Acc);
		}

		public bool Contains(string Serv, string Uname)
		{
			foreach (var a in acclist)
			{
				if (a.Equals(Serv, Uname))
				{
					return true;
				}
			}
			return false;
		}

		protected virtual void OnTimer(object sender, System.Timers.ElapsedEventArgs e)
		{
			if (e.SignalTime.Second % 7 == 0)
			{
				UpdateTimeline();
			}
		}

		private void UpdateTimeline()
		{
			foreach (var acc in acclist)
			{
				if (acc.Logged)
				{
					BeginTimeline(acc);
				}
			}
		}
		protected virtual void BeginLogin(Account Acc)
		{
			Task t = new Task((p) => Acc.Login());
			tqueue.Add(t);
		}
		protected virtual void BeginSend(Account Acc, string text)
		{
			Task t = new Task((p) => Acc.Send((string)p), text);
			tqueue.Add(t);
		}
		protected virtual void BeginSend(Account Acc, string text, string replyId)
		{
			Task t = new Task((p) => Acc.Send(text, replyId), text);
			tqueue.Add(t);
		}
		protected virtual void BeginTimeline(Account Acc)
		{
			if (Acc.IsDominatedBy(this))
			{
				Task t = new Task((p) => Acc.FetchTimeline((Session)p), this);
				tqueue.Add(t);
			}
		}

		protected Account GetAccount(string Serv, string Uname)
		{
			foreach (var acc in acclist)
			{
				if (acc.Equals(Serv, Uname))
				{
					return acc;
				}
			}
			return null;
		}

		protected virtual void OnLogin(Account sender, ApiResults r)
		{
			var lr = r as LoginResults;
			if (lr != null)
			{
				var temp = Interlocked.CompareExchange(ref LoginResponse, null, null);
				if (temp != null)
					temp(this, lr);

				switch (lr.status)
				{
				case HttpStatusCode.OK:
					BeginTimeline(sender);
					break;

				default:
					Detach(sender);
					break;
				}
			}
		}
		protected virtual void OnSend(Account sender, ApiResults r)
		{
			var sr = r as SendResults;
			if (sr != null)
			{
				var temp = Interlocked.CompareExchange(ref SendResponse, null, null);
				if (temp != null)
					temp(this, sr);
			}
		}
		protected virtual void OnTimeline(Account sender, ApiResults r)
		{
			var tlr = r as TLResults;
			if (tlr != null)
			{
				NewPostEventArgs newPost = new NewPostEventArgs();
				newPost.posts = new List<Post>();
				foreach (var p in tlr.posts)
				{
					if (!posts.ContainsKey(p.id))
					{
						var post = new Post(p, sender);
						newPost.posts.Add(post);
						posts.Add(post.post.id, post);
					}
				}

				if (newPost.posts.Count > 0)
				{
					var temp = Interlocked.CompareExchange(ref NewPost, null, null);
					if (temp != null)
						temp(this, newPost);
				}
			}
		}

		public virtual void Dispose()
		{
			Dispose(true);
		}
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				// If this should be put here inside the "if"?
				foreach (var acc in acclist)
				{
					Detach(acc);
				}
				acclist.Clear();
				posts.Clear();
				timer.Elapsed -= new System.Timers.ElapsedEventHandler(OnTimer);
			}
		}

		// ------------------------------------------------ //
		static private System.Timers.Timer timer = new System.Timers.Timer(1000);
	}
}
